package com.chatplus.application.service.file.impl;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import com.chatplus.application.common.exception.BadRequestException;
import com.chatplus.application.common.logging.SouthernQuietLogger;
import com.chatplus.application.common.logging.SouthernQuietLoggerFactory;
import com.chatplus.application.common.util.FileUtils;
import com.chatplus.application.dao.file.FileDao;
import com.chatplus.application.domain.entity.file.FileEntity;
import com.chatplus.application.domain.vo.file.UpLoadFileVo;
import com.chatplus.application.enumeration.FileChannelEnum;
import com.chatplus.application.file.core.OssClient;
import com.chatplus.application.file.dto.FileConfigDto;
import com.chatplus.application.file.entity.UploadResult;
import com.chatplus.application.file.enumd.AccessPolicyType;
import com.chatplus.application.service.basedata.ConfigService;
import com.chatplus.application.service.file.FileService;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Collection;
import java.util.List;

@Service("fileServiceOss")
public class OssFileServiceImpl extends FileService {

    private static final SouthernQuietLogger LOGGER = SouthernQuietLoggerFactory.getLogger(OssFileServiceImpl.class);
    private final String contextPath;

    public OssFileServiceImpl(FileDao fileDao
            , ConfigService configService
            , ServerProperties serverProperties) {
        super(configService, fileDao);
        this.contextPath = serverProperties.getServlet().getContextPath();
    }

    private OssClient getStorage() {
        return new OssClient(getFileConfig());
    }

    /**
     * 上传文件远程调用SDK
     */
    @Override
    public UpLoadFileVo uploadFile(MultipartFile scrFile, Long userId) {
        FileEntity fileEntity = upload(null, userId, scrFile);
        UpLoadFileVo vo = new UpLoadFileVo(true,
                fileEntity.getId().toString(),
                fileEntity.getOriginalName(),
                fileEntity.getHashId(),
                fileEntity.getUrl(),
                fileEntity.getContentType(),
                fileEntity.getFileSuffix());
        FileConfigDto fileConfig = getFileConfig();
        String accessBaseUrl = fileConfig.getAccessBaseUrl();
        String accessPolicy = fileConfig.getAccessPolicy();
        if (StringUtils.isNotEmpty(accessBaseUrl) && accessPolicy.equals(AccessPolicyType.PRIVATE.getType())) {
            String url;
            String contentType = vo.getContentType();
            UriComponentsBuilder builder = UriComponentsBuilder.fromUri(URI.create(accessBaseUrl)).replaceQuery("");
            if (contentType.startsWith("image")) {
                url = builder.replacePath(contextPath + "/api/image/{hashId}").build(vo.getHashId()).toString();
            } else {
                url = builder.replacePath(contextPath + "/api/file/{hashId}").build(vo.getHashId()).toString();
            }
            vo.setUrl(url);
        }
        return vo;
    }

    private FileEntity upload(String fileName, Long userId, MultipartFile file) {
        String originalFileName = file.getOriginalFilename();
        if (StringUtils.isNotEmpty(fileName)) {
            originalFileName = fileName;
        }
        String suffix = "";
        if (StringUtils.isNotBlank(originalFileName)) {
            suffix = StringUtils.substring(originalFileName, originalFileName.lastIndexOf("."), originalFileName.length());
        }
        String contentType;
        String hashId;
        try (InputStream inputStream = file.getInputStream()) {
            contentType = tika.detect(inputStream);
            inputStream.reset();
            hashId = DigestUtils.sha256Hex(inputStream);
        } catch (Exception e) {
            LOGGER.message("文件上传失败-hash获取失败").exception(e).error();
            throw new BadRequestException("文件上传失败-hash获取失败");
        }
        FileEntity fileEntity = getByHashId(hashId);
        // 如果已经存在相同的文件而且没有被删除，则直接返回
        if (fileEntity != null) {
            return fileEntity;
        }
        UploadResult uploadResult;
        try {
            uploadResult = getStorage().uploadSuffix(file.getBytes(), suffix, contentType);
        } catch (IOException e) {
            LOGGER.message("文件上传失败").exception(e).error();
            throw new BadRequestException("文件上传失败");
        }
        // 保存文件信息
        return buildResultEntity(originalFileName, userId, hashId, contentType, suffix, uploadResult);
    }

    private FileEntity buildResultEntity(String originalFileName, Long userId, String hashId, String contentType, String suffix, UploadResult uploadResult) {
        FileEntity oss = new FileEntity();
        oss.setUrl(uploadResult.getUrl());
        oss.setFileSuffix(suffix);
        oss.setFilePath(uploadResult.getFilename());
        oss.setOriginalName(originalFileName);
        oss.setService(getUploadChannel().getValue());
        oss.setContentType(contentType);
        oss.setUserId(userId);
        // 目前只作为标记使用，因为相同的文件hashId
        oss.setHashId(hashId);
        fileDao.insert(oss);
        return oss;
    }


    @Override
    public InputStream getInputStream(Long id) {
        FileEntity fileEntity = fileDao.selectById(id);
        if (fileEntity == null) {
            throw new BadRequestException("文件不存在或已丢失");
        }
        InputStream inputStream = getStorage().getObjectContent(fileEntity.getUrl());
        if (inputStream == null) {
            throw new BadRequestException("文件不存在或已丢失");
        }
        return inputStream;
    }

    @Override
    public FileChannelEnum getUploadChannel() {
        return FileChannelEnum.OSS;
    }

    @Override
    public void deleteByIds(Collection<Long> ids) {
        List<FileEntity> list = fileDao.selectBatchIds(ids);
        for (FileEntity sysOss : list) {
            //TODO 这步后续做成异步的，因为minio的文件删除有时候会卡住
            getStorage().delete(sysOss.getUrl());
        }
        fileDao.deleteBatchIds(ids);
    }

    @Override
    public void download(Long ossId, String fileName, HttpServletResponse response) {
        FileEntity sysOss = fileDao.selectById(ossId);
        if (ObjectUtil.isNull(sysOss)) {
            throw new BadRequestException("文件数据不存在!");
        }
        if (fileName == null) {
            fileName = sysOss.getOriginalName();
        }
        FileUtils.setAttachmentResponseHeader(response, fileName);
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
        try (InputStream inputStream = getStorage().getObjectContent(sysOss.getUrl())) {
            int available = inputStream.available();
            IoUtil.copy(inputStream, response.getOutputStream(), available);
        } catch (Exception e) {
            LOGGER.message("文件下载失败").exception(e).error();
            throw new BadRequestException("文件下载失败");
        }
    }
}
